package util;

import java.awt.Polygon;
import java.awt.geom.Point2D;

public class OAUtil {

	public static void verifyNotNull(Object obj, String paramName) {
		if (obj == null) {
			throw new NullPointerException("Parameter " + paramName
					+ " can not be null.");
		}
	}

	public static void verifyNotLessThanOne(int param, String paramName) {
		if (param < 1) {
			throw new IllegalArgumentException("Parameter " + paramName
					+ " can not be less than 1.");
		}
	}
	
	/**
	 * Takes a goal point and a line segment represented by two points. Returns
	 * the point on the line segment that is closest to the goal point.
	 * 
	 * @param goal
	 *            The point you are trying to get close to.
	 * @param segStart
	 *            The starting point of the line segment.
	 * @param segEnd
	 *            The end point of the line segment.
	 * @return The point on the line segment that is closest to the goal point.
	 *         This may be one of the two end points or any point in between.
	 */
	public static Point2D closestPointOnLineSegment(Point2D goal,
			Point2D segStart, Point2D segEnd) {

		float A = (float) (goal.getX() - segStart.getX());
		float B = (float) (goal.getY() - segStart.getY());
		float C = (float) (segEnd.getX() - segStart.getX());
		float D = (float) (segEnd.getY() - segStart.getY());

		float dot = (A * C) + (B * D);
		float len_sq = (C * C) + (D * D);
		float param = dot / len_sq;

		Point2D projectedPoint = new Point2D.Double();

		if (param < 0) {
			projectedPoint = segStart;
		} else if (param > 1) {
			projectedPoint = segEnd;
		} else {
			projectedPoint.setLocation(segStart.getX() + (param * C), segStart
					.getY()
					+ (param * D));
		}

		return projectedPoint;
	}

	public static Point2D getPolygonPointClosestTo(Point2D goal, Polygon shape) {
		Point2D answer = null;

		int numberOfPoints = shape.npoints;
		int[] xPoints = shape.xpoints;
		int[] yPoints = shape.ypoints;

		for (int i = 0; i < numberOfPoints; i++) {
			Point2D segStart = new Point2D.Double(xPoints[i], yPoints[i]);
			Point2D segEnd = new Point2D.Double(
					xPoints[(i + 1) % numberOfPoints], yPoints[(i + 1)
							% numberOfPoints]);

			Point2D closestPoint = closestPointOnLineSegment(goal, segStart,
					segEnd);
			if (answer == null
					|| goal.distance(closestPoint) < goal.distance(answer)) {
				answer = closestPoint;
			}
		}

		return answer;
	}
	
	public static Point2D getInnerPolygonPointClosetTo(Point2D goal, Polygon shape) {
		
		// First check if the point is already inside the shape
		if(shape.contains(goal)) {
			return goal;
		}
		
		Point2D candidatePoint = getPolygonPointClosestTo(goal, shape);
		
		if(shape.contains(candidatePoint)) {
			return candidatePoint;
		}
		
		// search the eight surrounding points
		candidatePoint.setLocation(candidatePoint.getX() - 1, candidatePoint.getY() - 1);
		if(shape.contains(candidatePoint)) {
			return candidatePoint;
		}
		
		candidatePoint.setLocation(candidatePoint.getX() + 1, candidatePoint.getY());
		if(shape.contains(candidatePoint)) {
			return candidatePoint;
		}
		
		candidatePoint.setLocation(candidatePoint.getX() + 1, candidatePoint.getY());
		if(shape.contains(candidatePoint)) {
			return candidatePoint;
		}
		
		candidatePoint.setLocation(candidatePoint.getX(), candidatePoint.getY() + 1);
		if(shape.contains(candidatePoint)) {
			return candidatePoint;
		}
		
		candidatePoint.setLocation(candidatePoint.getX(), candidatePoint.getY() + 1);
		if(shape.contains(candidatePoint)) {
			return candidatePoint;
		}
		
		candidatePoint.setLocation(candidatePoint.getX() - 1, candidatePoint.getY());
		if(shape.contains(candidatePoint)) {
			return candidatePoint;
		}
		
		candidatePoint.setLocation(candidatePoint.getX() - 1, candidatePoint.getY());
		if(shape.contains(candidatePoint)) {
			return candidatePoint;
		}
		
		candidatePoint.setLocation(candidatePoint.getX(), candidatePoint.getY() - 1);
		if(shape.contains(candidatePoint)) {
			return candidatePoint;
		}
		
		throw new RuntimeException("Closest point in polygon could not be found.");

	}
}
